﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace NetJsWire.Design
{
    internal class SelectionFrame
    {

        public SelectionFrame( Control control )
        {
            if ( control == null )
                throw new ArgumentNullException( "control" );

            _control = control;
        }


        private Rectangle _bounds;
        private Control _control;
        private Rectangle[] _handles = new Rectangle[ 8 ];
        private GrabHandle _handle = GrabHandle.None;
        private const int BORDER_SIZE = 7;


        #region Properties
        private enum GrabHandle
        {
            None = -1,
            TopLeft = 0,
            TopMiddle,
            TopRight,
            Right,
            BottomRight,
            BottomMiddle,
            BottomLeft,
            Left,
            Border // the border surrounding the control.
        }

        public Rectangle Bounds
        {
            get
            {
                _bounds.X = _control.Location.X - BORDER_SIZE;
                _bounds.Y = _control.Location.Y - BORDER_SIZE;
                _bounds.Width = _control.Width + BORDER_SIZE * 2;
                _bounds.Height = _control.Height + BORDER_SIZE * 2;

                return _bounds;
            }
            set
            {
                _bounds = value;
                _control.Bounds = _bounds;
            }
        }

        private SelectionRules SelectionRules
        {
            get
            {
                SelectionRules result = SelectionRules.AllSizeable;

                if ( _control.Site != null )
                {
                    IDesignerHost host = _control.Site.GetService( typeof( IDesignerHost ) ) as IDesignerHost;
                    if ( host != null )
                    {
                        ControlDesigner designer = host.GetDesigner( _control ) as ControlDesigner;
                        if ( designer != null )
                            result = designer.SelectionRules;
                    }
                }

                return result;
            }
        }

        public Control Control
        {
            get { return _control; }
            set
            {
                if ( value != null )
                    _control = value;
            }
        }

        public Control Parent
        {
            get
            {
                if ( _control.Parent == null )
                    return _control;
                else
                    return _control.Parent;
            }
        }

        private GrabHandle GrabHandleSelected
        {
            get { return _handle; }
            set { _handle = value; }
        }

        private bool PrimarySelection
        {
            get
            {
                bool result = false;
                if ( this.Control != null && this.Control.Site != null )
                {
                    ISelectionService selection = this.Control.Site.GetService( typeof( ISelectionService ) ) as ISelectionService;
                    if ( selection != null && selection.PrimarySelection == this.Control )
                        result = true;
                }
                return result;
            }
        }
        #endregion


        #region Drawing
        public void OnPaint( Graphics gfx )
        {
            DrawFrame( gfx );
            DrawGrabHandles( gfx );
        }

        private void DrawGrabHandles( Graphics gfx )
        {
            GraphicsState state = gfx.Save();
            gfx.TranslateTransform( this.Bounds.X, this.Bounds.Y );

            for ( int i = 0; i < _handles.Length; i++ )
            {
                _handles[ i ].Width = BORDER_SIZE;
                _handles[ i ].Height = BORDER_SIZE;
            }

            SelectionRules rules = SelectionRules;
            bool primarySelection = this.PrimarySelection;
            bool enabled = false;

            _handles[ ( int ) GrabHandle.TopLeft ].Location = new Point( 0, 0 );
            if ( this.CheckSelectionRules( rules, SelectionRules.TopSizeable | SelectionRules.LeftSizeable ) )
                enabled = true;

            ControlPaint.DrawGrabHandle( gfx, _handles[ ( int ) GrabHandle.TopLeft ], primarySelection, enabled );
            enabled = false;

            _handles[ ( int ) GrabHandle.TopMiddle ].Location = new Point( ( this.Bounds.Width - BORDER_SIZE ) / 2, 0 );
            if ( this.CheckSelectionRules( rules, SelectionRules.TopSizeable ) )
                enabled = true;

            ControlPaint.DrawGrabHandle( gfx, _handles[ ( int ) GrabHandle.TopMiddle ], primarySelection, enabled );
            enabled = false;

            _handles[ ( int ) GrabHandle.TopRight ].Location = new Point( this.Bounds.Width - BORDER_SIZE, 0 );
            if ( this.CheckSelectionRules( rules, SelectionRules.TopSizeable | SelectionRules.RightSizeable ) )
                enabled = true;

            ControlPaint.DrawGrabHandle( gfx, _handles[ ( int ) GrabHandle.TopRight ], primarySelection, enabled );
            enabled = false;

            _handles[ ( int ) GrabHandle.Right ].Location = new Point( this.Bounds.Width - BORDER_SIZE,
                                             ( this.Bounds.Height - BORDER_SIZE ) / 2 );
            if ( this.CheckSelectionRules( rules, SelectionRules.RightSizeable ) )
                enabled = true;

            ControlPaint.DrawGrabHandle( gfx, _handles[ ( int ) GrabHandle.Right ], primarySelection, enabled );
            enabled = false;

            _handles[ ( int ) GrabHandle.BottomRight ].Location = new Point( this.Bounds.Width - BORDER_SIZE,
                                            this.Bounds.Height - BORDER_SIZE );
            if ( this.CheckSelectionRules( rules, SelectionRules.BottomSizeable | SelectionRules.RightSizeable ) )
                enabled = true;

            ControlPaint.DrawGrabHandle( gfx, _handles[ ( int ) GrabHandle.BottomRight ], primarySelection, enabled );
            enabled = false;

            _handles[ ( int ) GrabHandle.BottomMiddle ].Location = new Point( ( this.Bounds.Width - BORDER_SIZE ) / 2,
                                             this.Bounds.Height - BORDER_SIZE );
            if ( this.CheckSelectionRules( rules, SelectionRules.BottomSizeable ) )
                enabled = true;

            ControlPaint.DrawGrabHandle( gfx, _handles[ ( int ) GrabHandle.BottomMiddle ], primarySelection, enabled );
            enabled = false;

            _handles[ ( int ) GrabHandle.BottomLeft ].Location = new Point( 0, this.Bounds.Height - BORDER_SIZE );
            if ( this.CheckSelectionRules( rules, SelectionRules.BottomSizeable | SelectionRules.LeftSizeable ) )
                enabled = true;

            ControlPaint.DrawGrabHandle( gfx, _handles[ ( int ) GrabHandle.BottomLeft ], primarySelection, enabled );
            enabled = false;

            _handles[ ( int ) GrabHandle.Left ].Location = new Point( 0, ( this.Bounds.Height - BORDER_SIZE ) / 2 );
            if ( this.CheckSelectionRules( rules, SelectionRules.LeftSizeable ) )
                enabled = true;

            ControlPaint.DrawGrabHandle( gfx, _handles[ ( int ) GrabHandle.Left ], primarySelection, enabled );
            gfx.Restore( state );
        }

        protected void DrawFrame( Graphics gfx )
        {
            Color negativeColor = Color.FromArgb( ( byte ) ~( _control.Parent.BackColor.R ),
                                                    ( byte ) ~( _control.Parent.BackColor.G ),
                                                    ( byte ) ~( _control.Parent.BackColor.B ) );
            Pen pen = new Pen( new HatchBrush( HatchStyle.Percent30, negativeColor, Color.FromArgb( 0 ) ), BORDER_SIZE );
            gfx.DrawRectangle( pen, this.Control.Bounds );
        }
        #endregion


        #region Dragging
        private bool _resizing = false;


        public bool SetCursor( int x, int y )
        {
            bool modified = false;

            if ( !_resizing )
            {
                GrabHandle handle = PointToGrabHandle( this.PointToClient( Control.MousePosition ) );
                if ( handle != GrabHandle.None )
                    modified = true;

                if ( handle == GrabHandle.TopLeft )
                    Cursor.Current = Cursors.SizeNWSE;
                else if ( handle == GrabHandle.TopMiddle )
                    Cursor.Current = Cursors.SizeNS;
                else if ( handle == GrabHandle.TopRight )
                    Cursor.Current = Cursors.SizeNESW;
                else if ( handle == GrabHandle.Right )
                    Cursor.Current = Cursors.SizeWE;
                else if ( handle == GrabHandle.BottomRight )
                    Cursor.Current = Cursors.SizeNWSE;
                else if ( handle == GrabHandle.BottomMiddle )
                    Cursor.Current = Cursors.SizeNS;
                else if ( handle == GrabHandle.BottomLeft )
                    Cursor.Current = Cursors.SizeNESW;
                else if ( handle == GrabHandle.Left )
                    Cursor.Current = Cursors.SizeWE;
                else
                    Cursor.Current = Cursors.Default;
            }
            return modified;
        }

        // container coordinates
        public void ResizeBegin( int x, int y )
        {
            this.GrabHandleSelected = PointToGrabHandle( this.PointToClient( this.Parent.PointToScreen( new Point( x, y ) ) ) );

            if ( this.GrabHandleSelected != GrabHandle.None )
                _resizing = true;
        }

        private bool CheckSelectionRules( SelectionRules rules, SelectionRules toCheck )
        {
            return ( ( rules & toCheck ) == toCheck );
        }

        // container coordinates returns deltaBounds
        public Rectangle ResizeContinue( int x, int y )
        {
            //Console.WriteLine ("ResizeContinue: " + x + " : " + y);
            //Console.WriteLine ("GrabHandleSelected: " + GrabHandleSelected);

            Rectangle bounds = ( Rectangle ) TypeDescriptor.GetProperties( _control )[ "Bounds" ].GetValue( _control );
            Rectangle deltaBounds = bounds;
            Point pointerLocation = new Point( x, y );
            SelectionRules rules = SelectionRules;
            int top, height, left, width = 0;

            if ( _resizing && this.GrabHandleSelected != GrabHandle.None && rules != SelectionRules.Locked )
            {
                if ( this.GrabHandleSelected == GrabHandle.TopLeft &&
                        CheckSelectionRules( rules, SelectionRules.LeftSizeable | SelectionRules.TopSizeable ) )
                {

                    top = _control.Top;
                    height = _control.Height;
                    left = _control.Left;
                    width = _control.Width;

                    if ( pointerLocation.Y < _control.Bottom )
                    {
                        top = pointerLocation.Y;
                        height = _control.Bottom - pointerLocation.Y;
                    }
                    if ( pointerLocation.X < _control.Right )
                    {
                        left = pointerLocation.X;
                        width = _control.Right - pointerLocation.X;
                        bounds = new Rectangle( left, top, width, height );
                    }
                }
                else if ( this.GrabHandleSelected == GrabHandle.TopRight &&
                        CheckSelectionRules( rules, SelectionRules.TopSizeable | SelectionRules.RightSizeable ) )
                {

                    top = _control.Top;
                    height = _control.Height;
                    width = _control.Width;

                    if ( pointerLocation.Y < _control.Bottom )
                    {
                        top = pointerLocation.Y;
                        height = _control.Bottom - pointerLocation.Y;
                    }
                    width = pointerLocation.X - _control.Left;
                    bounds = new Rectangle( _control.Left, top, width, height );
                }
                else if ( GrabHandleSelected == GrabHandle.TopMiddle && CheckSelectionRules( rules, SelectionRules.TopSizeable ) )
                {
                    if ( pointerLocation.Y < _control.Bottom )
                    {
                        top = pointerLocation.Y;
                        height = _control.Bottom - pointerLocation.Y;
                        bounds = new Rectangle( _control.Left, top, _control.Width, height );
                    }
                }
                else if ( this.GrabHandleSelected == GrabHandle.Right && CheckSelectionRules( rules, SelectionRules.RightSizeable ) )
                {
                    width = pointerLocation.X - _control.Left;
                    bounds = new Rectangle( _control.Left, _control.Top, width, _control.Height );
                }
                else if ( this.GrabHandleSelected == GrabHandle.BottomRight &&
                        CheckSelectionRules( rules, SelectionRules.BottomSizeable | SelectionRules.RightSizeable ) )
                {

                    width = pointerLocation.X - _control.Left;
                    height = pointerLocation.Y - _control.Top;
                    bounds = new Rectangle( _control.Left, _control.Top, width, height );
                }
                else if ( GrabHandleSelected == GrabHandle.BottomMiddle && CheckSelectionRules( rules, SelectionRules.BottomSizeable ) )
                {
                    height = pointerLocation.Y - _control.Top;
                    bounds = new Rectangle( _control.Left, _control.Top, _control.Width, height );
                }
                else if ( GrabHandleSelected == GrabHandle.BottomLeft &&
                        CheckSelectionRules( rules, SelectionRules.BottomSizeable | SelectionRules.LeftSizeable ) )
                {

                    height = _control.Height;
                    left = _control.Left;
                    width = _control.Width;

                    if ( pointerLocation.X < _control.Right )
                    {
                        left = pointerLocation.X;
                        width = _control.Right - pointerLocation.X;
                    }
                    height = pointerLocation.Y - _control.Top;
                    bounds = new Rectangle( left, _control.Top, width, height );
                }
                else if ( GrabHandleSelected == GrabHandle.Left && CheckSelectionRules( rules, SelectionRules.LeftSizeable ) )
                {
                    if ( pointerLocation.X < _control.Right )
                    {
                        left = pointerLocation.X;
                        width = _control.Right - pointerLocation.X;
                        bounds = new Rectangle( left, _control.Top, width, _control.Height );
                    }
                }

                //Console.WriteLine ("bounds: " + bounds.ToString ());
                TypeDescriptor.GetProperties( _control )[ "Bounds" ].SetValue( _control, bounds );

            }

            this.Parent.Refresh();
            deltaBounds.X = bounds.X - deltaBounds.X;
            deltaBounds.Y = bounds.Y - deltaBounds.Y;
            deltaBounds.Height = bounds.Height - deltaBounds.Height;
            deltaBounds.Width = bounds.Width - deltaBounds.Width;
            return deltaBounds;
        }


        public void ResizeEnd( bool cancel )
        {
            this.GrabHandleSelected = GrabHandle.None;
            _resizing = false;
        }

        public void Resize( Rectangle deltaBounds )
        {
            SelectionRules rules = SelectionRules;

            if ( this.CheckSelectionRules( rules, SelectionRules.Locked ) || !this.CheckSelectionRules( rules, SelectionRules.Moveable ) )
                return;

            Rectangle bounds = ( Rectangle ) TypeDescriptor.GetProperties( _control )[ "Bounds" ].GetValue( _control );

            if ( CheckSelectionRules( rules, SelectionRules.LeftSizeable ) )
            {
                bounds.X += deltaBounds.X;
                bounds.Width += deltaBounds.Width;
            }
            if ( CheckSelectionRules( rules, SelectionRules.RightSizeable ) && !CheckSelectionRules( rules, SelectionRules.LeftSizeable ) )
            {
                bounds.Y += deltaBounds.Y;
                bounds.Width += deltaBounds.Width;
            }
            if ( CheckSelectionRules( rules, SelectionRules.TopSizeable ) )
            {
                bounds.Y += deltaBounds.Y;
                bounds.Height += deltaBounds.Height;
            }
            if ( CheckSelectionRules( rules, SelectionRules.BottomSizeable ) && !CheckSelectionRules( rules, SelectionRules.TopSizeable ) )
            {
                bounds.Height += deltaBounds.Height;
            }

            TypeDescriptor.GetProperties( _control )[ "Bounds" ].SetValue( _control, bounds );
        }
        #endregion


        #region Utility methods

        public bool HitTest( int x, int y )
        {
            if ( PointToGrabHandle( this.PointToClient( this.Parent.PointToScreen( new Point( x, y ) ) ) ) != GrabHandle.None )
                return true;
            else
                return false;
        }

        private GrabHandle PointToGrabHandle( Point pointerLocation )
        {
            GrabHandle result = GrabHandle.None;

            if ( IsCursorOnGrabHandle( pointerLocation, _handles[ 0 ] ) )
                result = GrabHandle.TopLeft;
            else if ( IsCursorOnGrabHandle( pointerLocation, _handles[ 1 ] ) )
                result = GrabHandle.TopMiddle;
            else if ( IsCursorOnGrabHandle( pointerLocation, _handles[ 2 ] ) )
                result = GrabHandle.TopRight;
            else if ( IsCursorOnGrabHandle( pointerLocation, _handles[ 3 ] ) )
                result = GrabHandle.Right;
            else if ( IsCursorOnGrabHandle( pointerLocation, _handles[ 4 ] ) )
                result = GrabHandle.BottomRight;
            else if ( IsCursorOnGrabHandle( pointerLocation, _handles[ 5 ] ) )
                result = GrabHandle.BottomMiddle;
            else if ( IsCursorOnGrabHandle( pointerLocation, _handles[ 6 ] ) )
                result = GrabHandle.BottomLeft;
            else if ( IsCursorOnGrabHandle( pointerLocation, _handles[ 7 ] ) )
                result = GrabHandle.Left;
            else
                result = GrabHandle.None;

            return result;
        }

        private bool IsCursorOnGrabHandle( Point pointerLocation, Rectangle handleRectangle )
        {
            if ( pointerLocation.X >= handleRectangle.X &&
                    pointerLocation.X <= handleRectangle.X + handleRectangle.Width &&
                    pointerLocation.Y >= handleRectangle.Y &&
                    pointerLocation.Y <= handleRectangle.Y + handleRectangle.Height )
            {
                return true;
            }
            return false;
        }

        private Point PointToClient( Point screenPoint )
        {
            Point pointerLocation = this.Parent.PointToClient( screenPoint );
            pointerLocation.X = pointerLocation.X - this.Bounds.X;
            pointerLocation.Y = pointerLocation.Y - this.Bounds.Y;
            return pointerLocation;
        }
        #endregion

    }
}
